/*
 * Created by IntelliJ IDEA.
 * User: amrk
 * Date: 24/01/2004
 * Time: 15:02:29
 */
package com.talios.jira.browser;

import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.table.TableView;
import com.talios.jira.browser.signals.JiraServerDetails;
import com.talios.jira.feeds.FeedBuilder;
import com.talios.jira.feeds.filters.*;
import com.talios.jira.idea.JiraConfiguration;
import com.talios.jira.idea.JiraConfigurationComponent;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import javax.swing.text.html.StyleSheet;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @jira INTPLUG-19 Find issues in project
 */
public class IssueBrowserPanel {
    private static final Logger LOGGER = Logger.getInstance("Jira Browser");

    private JPanel mainPanel;
    private JEditorPane description;

    private Project project;
    private JTree filterTree;
    private DefaultMutableTreeNode issuesByReporter;
    private DefaultMutableTreeNode issuesByComponent;
    private DefaultMutableTreeNode issuesByFixVersion;
    private DefaultMutableTreeNode issuesByResolution;
    private TableView issues;
    private JPanel issuesPanel;
    private JPopupMenu issueMenu;

    private WorkOnSelectedIssueAction workOnSelectedIssueAction = new WorkOnSelectedIssueAction();
    private StopWorkingOnIssueAction stopWorkingOnIssueAction = new StopWorkingOnIssueAction();
    private ViewSelectedIssueInBrowserAction viewSelectedIssueInBrowserAction = new ViewSelectedIssueInBrowserAction();
    private ResolveIssueAction resolveIssueAction = new ResolveIssueAction();
    private CommentIssueAction commentIssueAction = new CommentIssueAction();
    private FindIssuesAction findIssuesAction = new FindIssuesAction();
    private RememberColumnPositions rememberColumnPositions = new RememberColumnPositions();
    private JPanel toolBarPanel;
    private JiraConfiguration configuration;

    public IssueBrowserPanel(Project project, FeedBuilder feedBuilder) {
        this(project, feedBuilder, false);
    }

    public IssueBrowserPanel(Project project, FeedBuilder feedBuilder, final boolean readOnly) {

        this.project = project;

        JiraConfigurationComponent config = project.getComponent(JiraConfigurationComponent.class);
        configuration = config.getConfiguration();
        List<? extends Object> columnList = configuration.getColumnList();
        ColumnSetting[] columns = columnList.toArray(new ColumnSetting[]{});

        setupToolBar();

        issues = new TableView(new JiraIssuesTableModel(project, this, feedBuilder, columns));
        resetColumnWidths();

        issuesPanel.setLayout(new BorderLayout());
        issuesPanel.setBorder(BorderFactory.createEmptyBorder());
        issues.setBorder(BorderFactory.createEmptyBorder());
        issues.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        issues.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {

                JiraConfigurationComponent config = IssueBrowserPanel.this.project.getComponent(JiraConfigurationComponent.class);
                JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();

                if (issues.getSelectedRow() < model.getRowCount() && issues.getSelectedRow() > -1) {
                    JiraItem item = model.getItemAt(issues.getSelectedRow());

                    if (item != null) {

                        workOnSelectedIssueAction.putValue(Action.NAME, "Work on issue " + item.getKey());
                        stopWorkingOnIssueAction.setEnabled(config.getItemWorkedOn() != null && !readOnly);

                        String issueDescription = item.getDescription();

                        description.setText(issueDescription);
                        HTMLDocument document = (HTMLDocument) description.getDocument();
                        StyleSheet styleSheet = document.getStyleSheet();

                        styleSheet.addRule("P {font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-weight: bold; font-size : 10pt; text-align : justify;}");

                    }
                }
            }
        });
        issues.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent mouseEvent) {

                if (mouseEvent.getClickCount() == 1 && mouseEvent.getButton() == MouseEvent.BUTTON3) {
                    issueMenu.show(issues, mouseEvent.getX(), mouseEvent.getY());
                }

                if (mouseEvent.getClickCount() == 2 && mouseEvent.getButton() == MouseEvent.BUTTON1) {
                    viewSelectedIssueInBrowserAction.actionPerformed(null);
                }

            }
        });
        issueMenu = new JPopupMenu("Issues");
        issueMenu.add(new JMenuItem(workOnSelectedIssueAction));
        issueMenu.add(new JMenuItem(stopWorkingOnIssueAction));
        issueMenu.add(new JMenuItem(viewSelectedIssueInBrowserAction));
        issueMenu.add(new JMenuItem(resolveIssueAction));
        issueMenu.add(new JMenuItem(commentIssueAction));
        issueMenu.add(new JMenuItem(findIssuesAction));
        issueMenu.add(new JSeparator());
        issueMenu.add(new JMenuItem(rememberColumnPositions));
        //        issueMenu.add(new JSeparator());
        //        issueMenu.add(new JMenuItem("Properties"));


        if (readOnly) {
            workOnSelectedIssueAction.setEnabled(false);
            stopWorkingOnIssueAction.setEnabled(false);
            resolveIssueAction.setEnabled(false);
        }

        issuesPanel.add(new JScrollPane(issues), BorderLayout.CENTER);

        description.addHyperlinkListener(new HyperlinkListener() {
            /**
             * @jira INTPLUG-4 Support for Multiple Projects
             */
            public void hyperlinkUpdate(HyperlinkEvent e) {
                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                    JEditorPane pane = (JEditorPane) e.getSource();
                    if (e instanceof HTMLFrameHyperlinkEvent) {
                        HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e;
                        HTMLDocument doc = (HTMLDocument) pane.getDocument();
                        doc.processHTMLFrameHyperlinkEvent(evt);
                    } else {
                        try {
                            String url = e.getURL().toExternalForm();

                            JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
                            JiraItem item = model.getItemAt(issues.getSelectedRow());
                            JiraServerDetails server = item.getProject().getServer();

                            if (url.indexOf(server.getBaseurl()) != -1) {
                                StringBuffer buf = new StringBuffer();
                                buf.append(url);

                                if (url.indexOf("?") == -1) {
                                    buf.append('?');
                                } else {
                                    buf.append('&');
                                }

                                buf.append("os_username=");
                                buf.append(URLEncoder.encode(server.getUsername(), "UTF-8"));
                                buf.append("&os_password=");
                                buf.append(URLEncoder.encode(server.getPassword(), "UTF-8"));
                                buf.append("&reset=true");
                                url = buf.toString();
                            }
                            LOGGER.info("Browsing to: " + url);
                            pane.setPage(url);
                        } catch (Throwable t) {
                            LOGGER.error(t.getMessage(), t);
                        }
                    }
                }
            }
        });

        DefaultMutableTreeNode filterNode = new DefaultMutableTreeNode();
        filterNode.setUserObject(new EmptyFilter("Browse Issues By"));

        issuesByReporter = new DefaultMutableTreeNode();
        issuesByReporter.setUserObject(new EmptyFilter("Reporter"));
        filterNode.add(issuesByReporter);

        issuesByComponent = new DefaultMutableTreeNode();
        issuesByComponent.setUserObject(new EmptyFilter("Component"));
        filterNode.add(issuesByComponent);

        issuesByFixVersion = new DefaultMutableTreeNode();
        issuesByFixVersion.setUserObject(new EmptyFilter("Fix Version"));
        filterNode.add(issuesByFixVersion);

        issuesByResolution = new DefaultMutableTreeNode();
        issuesByResolution.setUserObject(new EmptyFilter("Resolution"));
        filterNode.add(issuesByResolution);

        filterTree.setModel(new DefaultTreeModel(filterNode, true));

        filterTree.getSelectionModel().addTreeSelectionListener(new /**
         * @jira INTPLUG-149 local variables called "classname" are immediately prefixed by a jira comment
         */TreeSelectionListener() {
            /**
             * @jira INTPLUG-149 local variables called "classname" are immediately prefixed by a jira comment
             */
            public void valueChanged(TreeSelectionEvent e) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) filterTree.getLastSelectedPathComponent();

                if (node == null) return;

                Object nodeInfo = node.getUserObject();

                if (nodeInfo instanceof FeedFilter) {
                    FeedFilter filter = (FeedFilter) nodeInfo;
                    JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
                    model.filterIssuesWith(filter);
                }
            }
        });

        Icon filterIcon = IconLoader.getIcon("/actions/traceInto.png");
        filterTree.setCellRenderer(new IssueBrowserRenderer(filterIcon));
    }

    private void resetColumnWidths() {
        TableColumnModel columnModel = issues.getColumnModel();
        Enumeration<TableColumn> columns = columnModel.getColumns();
        while (columns.hasMoreElements()) {
            TableColumn tableColumn = columns.nextElement();

            int modelIndex = tableColumn.getModelIndex();
            ColumnSetting field = (ColumnSetting) configuration.getColumnList().get(modelIndex);
            tableColumn.setWidth(field.getWidth());
            tableColumn.setPreferredWidth(field.getWidth());
        }
        issues.setColumnModel(columnModel);

    }

    private void setupToolBar() {
        ActionManager actionManager = ActionManager.getInstance();
        DefaultActionGroup viewGroup = new DefaultActionGroup();
        viewGroup.add(new RefreshMyIssuesAction());
        //        viewGroup.add(new QuickSearchForIssuesAction());

        //        viewGroup.add(new CreateIssueAction() );
        viewGroup.add(new CloseSearchAction());

        ActionToolbar viewToolbar = actionManager.createActionToolbar("MyPlugin.ViewToolbar", viewGroup, false);

        JComponent component = viewToolbar.getComponent();
        toolBarPanel.add(component);

    }

    private class RefreshMyIssuesAction extends AnAction {
        public RefreshMyIssuesAction() {
            super("Refresh", "Refresh Issues", IconLoader.getIcon("/actions/refresh.png"));
        }

        public void actionPerformed(AnActionEvent anActionEvent) {
            IssueBrowserPanel.this.refresh();
        }
    }

    /**
     * @jira INTPLUG-106 "Jira Browser" window cannot be closed with red cross.
     */
    private class CloseSearchAction extends AnAction {
        public CloseSearchAction() {
            super("Close", "Close Search", IconLoader.getIcon("/actions/cancel.png"));
        }

        /**
         * @jira INTPLUG-106 "Jira Browser" window cannot be closed with red cross.
         */
        public void actionPerformed(AnActionEvent event) {
            Project project = (Project) event.getDataContext().getData(DataConstants.PROJECT);
            JiraConfigurationComponent config = project.getComponent(JiraConfigurationComponent.class);
            config.closeSearchTab(IssueBrowserPanel.this);
        }

    }


    /**
     * @jira INTPLUG-170 Remove werx library and dependencies
     */
    public class WorkOnSelectedIssueAction extends AbstractAction {
        public WorkOnSelectedIssueAction() {
            putValue(Action.NAME, "Work on issue...");
        }

        /**
         * @jira INTPLUG-28  "Stop working on issue" menu item not refreshed on right click
         * @jira INTPLUG-170 Remove werx library and dependencies
         */
        public void actionPerformed(ActionEvent e) {
            JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
            JiraItem item = model.getItemAt(issues.getSelectedRow());

            IssueBrowserPanel.this.stopWorkingOnIssueAction.setEnabled(true);

            IssueManager.getInstance().startWorkOnIssue(project, item);
        }
    }

    public class StopWorkingOnIssueAction extends AbstractAction {
        public StopWorkingOnIssueAction() {
            putValue(Action.NAME, "Stop working on issue");
        }

        public void actionPerformed(ActionEvent e) {
            IssueManager.getInstance().stopWorkingOnIssues(project);
        }
    }

    public class ViewSelectedIssueInBrowserAction extends AbstractAction {
        public ViewSelectedIssueInBrowserAction() {
            putValue(Action.NAME, "View issue in browser");
        }

        public void actionPerformed(ActionEvent e) {
            viewSelectedIssueInBrowser();
        }


        /**
         * @jira INTPLUG-22 NullPointerException on checking external browser setting
         * @jira INTPLUG-4 Support for Multiple Projects
         */
        private void viewSelectedIssueInBrowser() {
            try {
                JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
                JiraItem item = model.getItemAt(issues.getSelectedRow());
                String key = item.getKey();

                JiraServerDetails server = item.getServer();

                Project project = IssueBrowserPanel.this.project;

                JiraConfigurationComponent config = project.getComponent(JiraConfigurationComponent.class);
                JiraConfiguration configuration = config.getConfiguration();
                // TODO: Need to handle these NullPointers properly
                if (configuration.getUseExternalBrowser()) {
                    String browser = config.getBrowserExecutable();
                    server.viewIssueInBrowser(browser, key);
                } else {
                    String issueUrl = server.buildIssueUrl(key, true);
                    description.setPage(issueUrl);
                }
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    public class ResolveIssueAction extends AbstractAction {
        public ResolveIssueAction() {
            putValue(Action.NAME, "Resolve issue");
        }

        public void actionPerformed(ActionEvent e) {

            JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
            JiraItem item = model.getItemAt(issues.getSelectedRow());

            IssueResolution issueResolution = new IssueResolution(IssueBrowserPanel.this.project);
            issueResolution.resolveIssue(item);
        }
    }

    public class CommentIssueAction extends AbstractAction {
        public CommentIssueAction() {
            putValue(Action.NAME, "Comment issue");
        }

        public void actionPerformed(ActionEvent e) {

            JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
            JiraItem item = model.getItemAt(issues.getSelectedRow());

            IssueComment issueComment = new IssueComment(IssueBrowserPanel.this.project);
            issueComment.commentIssue(item);
        }
    }

    public class IssueBrowserRenderer extends DefaultTreeCellRenderer {
        private Icon filterIcon;

        public IssueBrowserRenderer(Icon icon) {
            filterIcon = icon;
        }

        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
                                                      boolean leaf, int row, boolean hasFocus) {

            super.getTreeCellRendererComponent(tree, value, sel,
                    expanded, leaf, row,
                    hasFocus);
            if (leaf && isFilter(value)) {
                setIcon(filterIcon);
                setToolTipText("Filter issues.");
            } else {
                setToolTipText(null); //no tool tip
            }

            return this;
        }

        protected boolean isFilter(Object value) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;

            return node.getUserObject() instanceof FeedFilter;

        }
    }

    public class RememberColumnPositions extends AbstractAction {
        public RememberColumnPositions() {
            putValue(Action.NAME, "Remember column positions/sizes");
        }

        public void actionPerformed(ActionEvent e) {
            Enumeration<TableColumn> columns = issues.getColumnModel().getColumns();
            int columnIndex = 0;
            while (columns.hasMoreElements()) {
                TableColumn tableColumn = columns.nextElement();

                int modelIndex = tableColumn.getModelIndex();
                ColumnSetting field = (ColumnSetting) configuration.getColumnList().get(modelIndex);
                if (field != null) {
                    field.setPosition(columnIndex);
                    field.setWidth(tableColumn.getWidth());
                }
                columnIndex++;
            }

            WindowManager.getInstance().getStatusBar(project).setInfo("Saved column positions and widths for Jira Browser.");
        }
    }

    /**
     * @jira INTPLUG-19 Find issues in project
     */
    public class FindIssuesAction extends AbstractAction {
        /**
         * @jira INTPLUG-19 Find issues in project
         */
        public FindIssuesAction() {
            putValue(Action.NAME, "Find issue references");
        }

        public void actionPerformed(ActionEvent e) {

            JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
            JiraItem item = model.getItemAt(issues.getSelectedRow());
            IssueSearcher runnable = new IssueSearcher(project, item);

            IssuesUsageViewDescriptor issuesUsageViewDescriptor = new IssuesUsageViewDescriptor(item, runnable);

            // TODO: Fix usage view
//            UsageViewManager.getInstance(project).addContent("Occurences of " + item.getKey(), true, issuesUsageViewDescriptor, true, true);

            if (!issuesUsageViewDescriptor.getIssueReferences().isEmpty()) {
                ToolWindowManager.getInstance(project).getToolWindow("Find").show(new Runnable() {
                    public void run() {
                    }
                });
            }
        }
    }

    private Runnable getFilterTreeRunnable() {
        Runnable filterTreeRunnable = new Runnable() {
            public void run() {
                LOGGER.info("Refreshing filter tree.");

                JiraIssuesTableModel model = (JiraIssuesTableModel) issues.getModel();
                List<JiraItem> tableData = model.getTableData();

                // Get a distinct lists
                Set<String> reporterSet = new HashSet<String>();
                Set<String> componentSet = new HashSet<String>();
                Set<String> fixVersionSet = new HashSet<String>();
                Set<String> resolutionSet = new HashSet<String>();
                for (JiraItem jiraItem : tableData) {
                    reporterSet.add(jiraItem.getReporter());

                    String component = jiraItem.getComponent();
                    if (component != null && !"".equals(component)) {
                        componentSet.add(component);
                    }

                    String fixVersion = jiraItem.getFixVersion();
                    if (fixVersion != null && !"".equals(fixVersion)) {
                        fixVersionSet.add(fixVersion);
                    }

                    String resolution = jiraItem.getResolution();
                    if (resolution != null && !"".equals(resolution)) {
                        resolutionSet.add(resolution);
                    }
                }

                buildFilterNodes(issuesByReporter, reporterSet, FilterByReporter.class);
                buildFilterNodes(issuesByComponent, componentSet, FilterByComponent.class);
                buildFilterNodes(issuesByFixVersion, fixVersionSet, FilterByFixVersion.class);
                buildFilterNodes(issuesByResolution, resolutionSet, FilterByResolution.class);

                if (filterTree.getRowCount() > 1) {
                    filterTree.expandRow(filterTree.getRowCount() - 1);
                }
            }

        };
        return filterTreeRunnable;
    }

    private void buildFilterNodes(DefaultMutableTreeNode node, Set<String> set, Class aClass) {
        // Add each reporter to the filter menu

        //        assert aClass.isInstance(FeedFilter.class);

        node.removeAllChildren();
        try {
            for (Object aSet : set) {
                String item = (String) aSet;
                DefaultMutableTreeNode reporterNode = new DefaultMutableTreeNode();

                FeedFilter filter = (FeedFilter) aClass.newInstance();
                filter.setFilterOn(item);

                reporterNode.setUserObject(filter);
                reporterNode.setAllowsChildren(false);
                node.add(reporterNode);
            }
        } catch (IllegalAccessException e) {
            LOGGER.error("Unable to add filter: " + e.getMessage(), e);
        } catch (InstantiationException e) {
            LOGGER.error("Unable to add filter: " + e.getMessage(), e);
        }

    }

    public JPanel getMainPanel() {
        return mainPanel;
    }

    public void setMainPanel(JPanel mainPanel) {
        this.mainPanel = mainPanel;
    }

    public JEditorPane getDescription() {
        return description;
    }

    public void setDescription(JEditorPane description) {
        this.description = description;
    }

    public TableView getIssues() {
        return issues;
    }

    public void setIssues(TableView issues) {
        this.issues = issues;
    }

    /**
     * @jira INTPLUG-4 Support for Multiple Projects
     */
    public void refresh() {
        if (issues.getColumnModel().getColumnCount() != 0) {
            JiraIssuesTableModel jiraIssuesTableModel = (JiraIssuesTableModel) issues.getModel();
            jiraIssuesTableModel.refreshModel(getFilterTreeRunnable());
        }
    }

    public JiraIssuesTableModel getJiraModel() {
        return (JiraIssuesTableModel) issues.getModel();
    }

    public JPanel getToolBarPanel() {
        return toolBarPanel;
    }

}